﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Ink;
using System.ComponentModel;
using InkCanvasControls.Properties;
using InkCanvasEditingModes;

namespace InkCanvasControls
{
    /// <summary>
    /// Interaction logic for UserControl1.xaml
    /// </summary>
    public partial class InkCanvasControls : UserControl
    {
        #region Commands
        public static readonly RoutedUICommand PenCommand = new RoutedUICommand();
        public static readonly RoutedUICommand StrokeEraserCommand = new RoutedUICommand();
        public static readonly RoutedUICommand PointEraserCommand = new RoutedUICommand();
        public static readonly RoutedUICommand HighlighterCommand = new RoutedUICommand();
        public static readonly RoutedUICommand LassoCommand = new RoutedUICommand();
        public static readonly RoutedUICommand ClearCommand = new RoutedUICommand();
        public static readonly RoutedUICommand CutCommand = new RoutedUICommand();
        public static readonly RoutedUICommand CopyCommand = new RoutedUICommand();
        public static readonly RoutedUICommand PasteCommand = new RoutedUICommand();
        public static readonly RoutedUICommand UndoCommand = new RoutedUICommand();
        public static readonly RoutedUICommand RedoCommand = new RoutedUICommand();
        #endregion

        #region Members
        private PenStyles _styles;
        private DrawingAttributes _hiliteAttributes;
        private DrawingAttributes _penAttributes;
        private Point _canvasLastClickPoint = new Point(0, 0);
        private CommandStack _cmdStack;
        private int _editingOperationCount;

        #endregion

        #region Constructor
        public InkCanvasControls()
        {
            InitializeComponent();

            InitialisePenAttributes();
            InitialiseHiliteAttributes();

            DataContext = this;
        }

        private void InitialisePenAttributes()
        {
            _penAttributes = new DrawingAttributes();
            _penAttributes.Color = Colors.Black;
            _penAttributes.IgnorePressure = true;
            _penAttributes.StylusTip = StylusTip.Rectangle;
            _penAttributes.Height = 2;
            _penAttributes.Width = 2;
            _penAttributes.IsHighlighter = false;
            _penAttributes.FitToCurve = true;

            AttributesToStylesPallet(_penAttributes);
        }

        private void InitialiseHiliteAttributes()
        {
            _hiliteAttributes = new DrawingAttributes();
            _hiliteAttributes.Color = Colors.Yellow;
            _hiliteAttributes.IgnorePressure = true;
            _hiliteAttributes.StylusTip = StylusTip.Rectangle;
            _hiliteAttributes.Height = 25;
            _hiliteAttributes.Width = 5;
            _hiliteAttributes.IsHighlighter = true;
            _hiliteAttributes.FitToCurve = true;

            AttributesToStylesPallet(_hiliteAttributes);
        }
        #endregion

        #region Window UI event handlers
        void Canvas_Unloaded(object sender, RoutedEventArgs e)
        {
            CloseStyles();
        }
        #endregion

        #region Dependency Properties

        public int BarWidth
        {
            get { return (int)GetValue(BarWidthProperty); }
            set { SetValue(BarWidthProperty, value); }
        }

        public static readonly DependencyProperty BarWidthProperty =
            DependencyProperty.Register("BarWidth", typeof(int), typeof(InkCanvasControls), new PropertyMetadata(500));

        public bool UndoRedoSupport
        {
            get { return (bool)GetValue(UndoRedoProperty); }
            set { SetValue(UndoRedoProperty, value); }
        }

        public static readonly DependencyProperty UndoRedoProperty =
            DependencyProperty.Register("UndoRedoSupport", typeof(bool), typeof(InkCanvasControls), new PropertyMetadata(false));


        public InkCanvas Canvas
        {
            get { return (InkCanvas)GetValue(CanvasProperty); }
            set { SetValue(CanvasProperty, value); }
        }

        public static readonly DependencyProperty CanvasProperty =
            DependencyProperty.Register("Canvas", typeof(InkCanvas), typeof(InkCanvasControls), new PropertyMetadata(CanvasChanged));

        static void CanvasChanged(DependencyObject property, DependencyPropertyChangedEventArgs args)
        {
            (property as InkCanvasControls).CanvasChanged(args);
        }

        private void CanvasChanged(DependencyPropertyChangedEventArgs args)
        {
            InkCanvas canvas = args.NewValue as InkCanvas;
            if (canvas != null)
            {
                canvas.Loaded += new RoutedEventHandler(canvas_Loaded);
                canvas.PreviewMouseDown += new MouseButtonEventHandler(canvas_PreviewMouseDown);

                //UndoRedo
                _cmdStack = new CommandStack(canvas.Strokes);
                canvas.MouseUp += new MouseButtonEventHandler(canvas_MouseUp);
                canvas.Strokes.StrokesChanged += new StrokeCollectionChangedEventHandler(Strokes_StrokesChanged);
                canvas.SelectionMoving += new InkCanvasSelectionEditingEventHandler(canvas_SelectionMovingOrResizing);
                canvas.SelectionResizing += new InkCanvasSelectionEditingEventHandler(canvas_SelectionMovingOrResizing);
            }
        }

        private void canvas_PreviewMouseDown(object sender, MouseButtonEventArgs e)
        {
            _canvasLastClickPoint = e.GetPosition(sender as InkCanvas);
        }

        private void canvas_Loaded(object sender, RoutedEventArgs e)
        {
            if (_styles == null)
            {
                ShowStyles(PenStyles.ToolStyle.PEN);
                InitialiseClipboardContextMenu(sender as InkCanvas);
            }
        }

        private void InitialiseClipboardContextMenu(InkCanvas canvas)
        {
            ContextMenu menu = new ContextMenu();

            MenuItem cutItem = new MenuItem();
            cutItem.Header = "Cut";
            cutItem.Command = CutCommand;
            menu.Items.Add(cutItem);

            MenuItem copyItem = new MenuItem();
            copyItem.Header = "Copy";
            copyItem.Command = CopyCommand;
            menu.Items.Add(copyItem);

            MenuItem pasteItem = new MenuItem();
            pasteItem.Header = "Paste";
            pasteItem.Command = PasteCommand;
            menu.Items.Add(pasteItem);

            canvas.ContextMenu = menu;

            InitialiseClipboardCommands(canvas.ContextMenu);
        }

        private void InitialiseClipboardCommands(ContextMenu menu)
        {
            menu.CommandBindings.Add(new CommandBinding(CutCommand, CutCommandExecuted, CutCopyCanExecute));
            menu.CommandBindings.Add(new CommandBinding(CopyCommand, CopyCommandExecuted, CutCopyCanExecute));
            menu.CommandBindings.Add(new CommandBinding(PasteCommand, PasteCommandExecuted, PasteCanExecute));
        }
        #endregion

        #region Tool Command handlers
        public bool SomeStrokesSelected
        {
            get
            {
                if (Canvas != null)
                {
                    return Canvas.GetSelectedStrokes().Count > 0;
                }
                return false;
            }
        }

        private void ClearCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            StrokeCollection selectedStrokes = Canvas.GetSelectedStrokes();
            if (selectedStrokes.Count == 0)
            {
                if (MessageBox.Show(_styles, Settings.Default.ConfirmClearMsg, Settings.Default.ConfirmClear, MessageBoxButton.OKCancel, MessageBoxImage.Question) == MessageBoxResult.OK)
                {
                    Canvas.Strokes.Clear();
                }
                return;
            }
            else
            {
                Canvas.Strokes.Remove(selectedStrokes);
            }
        }

        private void ToolCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = Canvas != null;
            e.Handled = true;
        }

        private void PenCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.EditingMode = InkCanvasEditingMode.Ink;
            Canvas.DefaultDrawingAttributes = _penAttributes;

            ShowStyles(PenStyles.ToolStyle.PEN);
            e.Handled = true;
        }

        private void StrokeEraserCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.EditingMode = InkCanvasEditingMode.EraseByStroke;

            _styles.CurrentTool = PenStyles.ToolStyle.STROKE_ERASER;
            CloseStyles();
            e.Handled = true;
        }

        private void PointEraserCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.EditingMode = InkCanvasEditingMode.EraseByPoint;

            _styles.CurrentTool = PenStyles.ToolStyle.POINT_ERASER;
            CloseStyles();
            e.Handled = true;
        }

        private void HighlighterCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.EditingMode = InkCanvasEditingMode.Ink;
            Canvas.DefaultDrawingAttributes = _hiliteAttributes;

            ShowStyles(PenStyles.ToolStyle.HIGHLIGHTER);
            e.Handled = true;
        }

        private void LassoCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.EditingMode = InkCanvasEditingMode.Select;
            Canvas.SelectionChanged += new EventHandler(Canvas_SelectionChanged);

            ShowStyles(PenStyles.ToolStyle.PEN);
            e.Handled = true;
        }
        #endregion

        #region Clipboard Command handlers
        private void CutCopyCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = SomeStrokesSelected;
            e.Handled = true;
        }

        private void PasteCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            if (Canvas == null)
            {
                e.CanExecute = false;
            }
            else
            {
                e.CanExecute = Canvas.CanPaste();
            }
            e.Handled = true;
        }

        private void CutCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.CutSelection();
            e.Handled = true;
        }

        private void CopyCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.CopySelection();
            e.Handled = true;
        }

        private void PasteCommandExecuted(object sender, ExecutedRoutedEventArgs e)
        {
            Canvas.Paste(_canvasLastClickPoint);
            e.Handled = true;
        }
        #endregion

        #region Selection
        void Canvas_SelectionChanged(object sender, EventArgs e)
        {
            ShowCommonStrokeAttributesInPalette();
        }

        private void ShowCommonStrokeAttributesInPalette()
        {
            StrokeCollection selectedStrokes = Canvas.GetSelectedStrokes();
            if (selectedStrokes.Count > 0)
            {
                //Show current attributes (or nothing) in pallet
                StylusTip selectedTip = selectedStrokes[0].DrawingAttributes.StylusTip;
                Color selectedColour = selectedStrokes[0].DrawingAttributes.Color;
                double selectedWidth = selectedStrokes[0].DrawingAttributes.Width;

                if (_styles != null)
                {
                    foreach (Stroke stroke in selectedStrokes)
                    {
                        if (!stroke.DrawingAttributes.IsHighlighter)
                        {

                            if (stroke.DrawingAttributes.Color != selectedColour)
                            {
                                selectedColour = Colors.Transparent;
                            }
                            if (stroke.DrawingAttributes.Width != selectedWidth)
                            {
                                selectedWidth = 0.0;
                            }
                            if (stroke.DrawingAttributes.StylusTip != selectedTip)
                            {
                                selectedTip = (StylusTip)(-1);
                            }
                        }
                    }
                }
                _styles.SetPenColour(selectedColour);
                _styles.SetThickness(selectedWidth);
                _styles.SetTip(selectedTip);
            }
        }
        #endregion

        #region Appropriate pallet window
        private void ShowStyles(PenStyles.ToolStyle tool)
        {
            if ((_styles == null) || (_styles.Visibility != Visibility.Visible))
            {
                _styles = new PenStyles();
                _styles.PenColourChanged += new PenStyles.PenColourChangedEventHandler(_styles_PenColourChanged);
                _styles.HiliteColourChanged += new PenStyles.HiliteColourChangedEventHandler(_styles_HiliteColourChanged);
                _styles.ThicknessChanged += new PenStyles.ThicknessChangedEventHandler(_styles_ThicknessChanged);
                _styles.TipChanged += new PenStyles.TipChangedEventHandler(_styles_TipChanged);
                _styles.EraseModeChanged += new PenStyles.EraseModeChangedEventHandler(_styles_EraseModeChanged);
                _styles.Show();
                if (Canvas != null)
                {
                    Canvas.Unloaded += new RoutedEventHandler(Canvas_Unloaded);
                }
            }
            _styles.CurrentTool = tool;
            AttributesToStylesPallet(Canvas.DefaultDrawingAttributes);
       }

        void _styles_EraseModeChanged(InkCanvasEditingMode mode)
        {
            Canvas.EditingMode = mode;
        }

        void _styles_TipChanged(StylusTip tip)
        {
            StrokeCollection selectedStrokes = Canvas.GetSelectedStrokes();
            if (selectedStrokes.Count == 0)
            {
                _penAttributes.StylusTip = tip;
                Canvas.DefaultDrawingAttributes.StylusTip = tip;
            }
            else
            {
                foreach (Stroke stroke in selectedStrokes)
                {
                    if (!stroke.DrawingAttributes.IsHighlighter)
                    {
                        stroke.DrawingAttributes.StylusTip = tip;
                    }
                }
            }
        }

        void _styles_ThicknessChanged(double thickness)
        {
            StrokeCollection selectedStrokes = Canvas.GetSelectedStrokes();
            if (selectedStrokes.Count == 0)
            {
                _penAttributes.Width = thickness;
                _penAttributes.Height = thickness;

                Canvas.DefaultDrawingAttributes.Width = thickness;
                Canvas.DefaultDrawingAttributes.Height = thickness;
            }
            else
            {
                foreach (Stroke stroke in selectedStrokes)
                {
                    if (!stroke.DrawingAttributes.IsHighlighter)
                    {
                        stroke.DrawingAttributes.Width = thickness;
                        stroke.DrawingAttributes.Height = thickness;
                    }
                }
            }
        }

        void _styles_PenColourChanged(Color colour)
        {
            StrokeCollection selectedStrokes = Canvas.GetSelectedStrokes();
            if (selectedStrokes.Count == 0)
            {
                _penAttributes.Color = colour;
                Canvas.DefaultDrawingAttributes.Color = colour;
            }
            else
            {
                foreach (Stroke stroke in selectedStrokes)
                {
                    if (!stroke.DrawingAttributes.IsHighlighter)
                    {
                        stroke.DrawingAttributes.Color = colour;
                    }
                }
            }
        }

        void _styles_HiliteColourChanged(Color colour)
        {
            _hiliteAttributes.Color = colour;
            Canvas.DefaultDrawingAttributes.Color = colour;
        }

        private void CloseStyles()
        {
            if (_styles != null)
            {
                _styles.Close();
            }
        }
        #endregion

        #region UndoRedo Command handlers

        //Track when mouse or stylus goes up to increment the editingOperationCount for undo / redo
        private void canvas_MouseUp(object sender, MouseButtonEventArgs e)
        {
            _editingOperationCount++;
        }

        private void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
        {
            StrokeCollection added = new StrokeCollection(e.Added);
            IndexAllStrokes(e.Added);
            StrokeCollection removed = new StrokeCollection(e.Removed);

            if (e.Removed.Count > 0)
            {
                int rem = e.Removed[0].GetPropertyDataIds().Count();
                if (rem > 0)
                {
                    int index1 = (int)e.Removed[0].GetPropertyData(CommandItem.STROKE_INDEX_PROPERTY);
                    //MessageBox.Show(string.Format("Removed {0}", index1));
                }
            }

            CommandItem item = new StrokesAddedOrRemovedCI(_cmdStack, Canvas.EditingMode, added, removed, _editingOperationCount);
            _cmdStack.Enqueue(item);
        }

        private void IndexAllStrokes(StrokeCollection added)
        {
            foreach (Stroke stroke in added)
            {
                //Remember original index of each stroke in collection
                int strokeIndex = Canvas.Strokes.IndexOf(stroke);
                stroke.AddPropertyData(CommandItem.STROKE_INDEX_PROPERTY, strokeIndex);
            }
        }

        private void canvas_SelectionMovingOrResizing(object sender, InkCanvasSelectionEditingEventArgs e)
        {
            // Enforce stroke bounds to positive territory.
            Rect newRect = e.NewRectangle; Rect oldRect = e.OldRectangle;

            if (newRect.Top < 0d || newRect.Left < 0d)
            {
                Rect newRect2 =
                    new Rect(newRect.Left < 0d ? 0d : newRect.Left,
                                newRect.Top < 0d ? 0d : newRect.Top,
                                newRect.Width,
                                newRect.Height);

                e.NewRectangle = newRect2;
            }
            CommandItem item = new SelectionMovedOrResizedCI(_cmdStack, Canvas.GetSelectedStrokes(), newRect, oldRect, _editingOperationCount);
            _cmdStack.Enqueue(item);
        }

        private void UndoCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = _cmdStack == null ? false : _cmdStack.CanUndo;
            e.Handled = true;
        }

        private void UndoCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (_cmdStack != null)
            {
                _cmdStack.Undo();
            }
            e.Handled = true;
        }

        private void RedoCommandCanExecute(object sender, CanExecuteRoutedEventArgs e)
        {
            e.CanExecute = _cmdStack == null ? false : _cmdStack.CanRedo;
            e.Handled = true;
        }

        private void RedoCommand_Executed(object sender, ExecutedRoutedEventArgs e)
        {
            if (_cmdStack != null)
            {
                _cmdStack.Redo();
            }
            e.Handled = true;
        }
        #endregion

        #region Logic
        private void AttributesToStylesPallet(DrawingAttributes attributes)
        {
            if (_styles != null)
            {
                if (attributes.IsHighlighter)
                {
                    _styles.SetHiliteColour(_hiliteAttributes.Color);
                }
                else
                {
                    _styles.SetPenColour(attributes.Color);
                    _styles.SetThickness(attributes.Width);
                    _styles.SetTip(attributes.StylusTip);
                }
            }
        }
        #endregion
    }
}
